/*
	This script file is part of the Sonic 3 A.I.R. script software distribution.
	Copyright (C) 2017-2024 by Eukaryot

	Published under the GNU GPLv3 open source software license, see license.txt
	or https://www.gnu.org/licenses/gpl-3.0.en.html
*/


constant u16 LevelSelect.Background.targetInVRAM = 0x0000
constant u16 LevelSelect.TextTiles.targetInVRAM	 = 0x0200


//-------------------------------------------------------//
// Level Select Main Function                            //
//-------------------------------------------------------//

//# address-hook(0x007a74) end(0x007d20)
//# translated(0x007d64) end(0x007d6a)
function void LevelSelect()
{
	global.zone_act = 0
	FadeOutScreenBlocking()

#if STANDALONE
	Renderer.resetSprites()
#else
	set_status_register(0x2700)
#endif

	LevelSelect.init()

	Menu.FadeInAfterInit()

	// Perform main update now
	bool running = true
	while (running)
	{
		global.frame_state = 0x16

		// End point for a single frame
		//  -> Right here we place the entry point for the standalone version
		waitForNextFrame()
	@EntryPoint:

		running = LevelSelect.innerUpdate()
	}
}



//-------------------------------------------------------//
// Level Select Initialization                           //
//-------------------------------------------------------//

function void LevelSelect.init()
{
	// Disable active display for now
	VDP.Config.setActiveDisplay(false)

	fn0011ca()

	// Configure VDP registers
	VDP.Config.enableHInt(false)
	VDP.Config.setNameTableBasePlaneA(0xc000)
	VDP.Config.setNameTableBasePlaneB(0xe000)
	VDP.Config.setBackdropColor(0)
	VDP.Config.setRenderingModeConfiguration(false)
	VDP.Config.setPlayfieldSizeInPixels(512, 256)
	VDP.Config.setVerticalScrolling(false, 0)	// Horizontal scrolling mode without scroll mask

	zeroMemory(0xffffac00, 0x400)
	zeroMemory(0xffffb000, 0x2000)

	u16[0xfffffb00] = 0
	u32[0xfffffbfc] = 0xfffffb00
	Nemesis.loadDataToVRAM(0x0ca5e0, LevelSelect.TextTiles.targetInVRAM)		// Main Level Select Font

	Enigma.decompress(0x0cab54, 0xffff0000, (sprite_attribute.PALETTE.LINE3 | (LevelSelect.Background.targetInVRAM >> 5)))
	copyRectToVRAM(0xffff0000, 0xe000, 0x28, 0x1c)

	zeroMemory(0xffff0000, 0x460 * 2)

	A3 = 0xffff0000
	A1 = 0x0080e6		// Table of level select selection strings
	A5 = 0x0080c4		// Table of option positions
	D0 = 0
	D1.u16 = 0x10		// Number of entries

	while (D1.s16 >= 0)
	{
		D3.u16 = u16[(A5+=2)-2]
		A2 = A3 + D3.s16
		D2 = u8[(A1+=1)-1]		// Get string length
		D3.u16 = D2.u16

		while (D2.s16 >= 0)
		{
			// Reading and writing each character in string
			u16[(A2+=2)-2] = u8[(A1+=1)-1]
			--D2.s16
		}

		D2.u16 = 0x0d - D3.u16
		while (D2.s16 >= 0)
		{
			// Add empty space after string
			u16[(A2+=2)-2] = 0
			--D2.s16
		}

		u16[A2] = 0x11		// Level select act number "1"
		A2 += 0x50			// Next line
		u16[A2] = 0x12		// Level select act number "2"
		--D1.s16
	}

	u16[A2] = 0		// New line, reached end of list
	A2 -= 0x50		// Going back a line to Sound Test option
	u16[A2] = 0x1a		// Write second asterisk '*' for Sound Test option

	// Act number for second entry for Lava Reef
	u16[0xffff018a] = 0x13		// Level select act number "3"
	u16[0xffff01da] = 0x14		// Level select act number "4"

	copyRectToVRAM(0xffff0000, 0xc000, 0x28, 0x1c)

	// Character select digits
	D3 = 0
	fn007f62()

	global.characters = CHARS_SONIC_AND_TAILS
	u16[0xffffff02] = 0
	global.level_started = 0
	u16[0xfffff7f0] = 0

	A2 = 0x0081b0
	LevelTilesAnim.Default()

	Level.loadNonfadingPaletteData(0x04)		// Level Select

	copyMemory(0xfffffcc0, 0xfffffc40, 0x20)
	zeroMemory(0xfffffc40, 0x20)

	playMusic(MUSIC_DATASELECT)

	global.demo_countdown = 1799	// 30 seconds (minus one frame)
	competition_mode.active = 0
	camera.position.x = 0
	camera.position.y = 0
	global.active_saveslot = 0
	level.giantrings_clear = 0
	checkpoint.number = 0
	global.stage_type = 0
	bluespheres.minigame_mode = 0

	u16[0xffffffe4] = 0
	u16[0xffffffe6] = 0
	global.frame_state = 0x16

#if STANDALONE
	// Corrections for widescreen
	for (u16 base = 0xe000; base < 0xee00; base += 0x80)
	{
		for (u16 x = 0; x < 0x30; x += 2)
		{
			setVRAM(base + x + 0x50, getVRAM(base + x + 0x08))
		}
	}

	// Foreground scroll offset
	u16[0xffffe000] = max(s16(getScreenExtend()) - 6, 0)

	// Write sound test digits (they are missing otherwise)
	VDP.setupVRAMWrite(0xcb46)
	LevelSelect.writeHexDigit(levelselect.soundtest >> 4,   0)
	LevelSelect.writeHexDigit(levelselect.soundtest & 0x0f, 0)

#endif

	waitForNextFrame()

	// Re-enable active display again
	VDP.Config.setActiveDisplay(true)
}



//-------------------------------------------------------//
// Level Select Update                                   //
//-------------------------------------------------------//

function bool LevelSelect.innerUpdate()
{
#if STANDALONE
	// Corrections for widescreen
	//  -> Actually no need to do this each frame, but I like to load level select save states and want it to be corrected after initialization
	for (u16 base = 0xe000; base < 0xee00; base += 0x80)
	{
		for (u16 x = 0; x < 0x30; x += 2)
		{
			setVRAM(base + x + 0x50, getVRAM(base + x + 0x08))
		}
	}

	// Foreground scroll offset
	u16[0xffffe000] = max(s16(getScreenExtend()) - 6, 0)
#endif

#if !STANDALONE
	set_status_register(0x2700)
#endif

	// White unhighlighted text
	D3 = 0
	fn007f62()

	// Menu controls
	fn007e64()

	// Yellow highlighted text
	D3.u16 = sprite_attribute.PALETTE.LINE3
	fn007f62()

#if !STANDALONE
	set_status_register(0x2300)
#endif

	A2 = 0x0081b0
	LevelTilesAnim.Default()

	if ((control.pad1.pressed | control.pad2.pressed) & CONTROL_START)
	{
		if (global.lock_on_state != 0)
		{
			// In Sonic & Knuckles without lock-on, any character selection that isn't Knuckles alone is set to Sonic Alone, skipping Tails alone and Sonic & Tails
			if (levelselect.characters < CHARS_KNUCKLES_ALONE)
				levelselect.characters = CHARS_SONIC_ALONE
		}

		global.characters = levelselect.characters
	#if STANDALONE
		// Handle Knuckles & Tails
		useKnucklesAndTails = (global.characters == CHARS_KNUCKLES_AND_TAILS)
		global.characters = min(global.characters, 3)
	#endif

		D0.u16 = u16[0x007d22 + levelselect.selection * 2]
		if (D0.u16 & 0x8000)
		{
			// Sound test
			global.game_mode = 0
			return true
		}

		if (D0.u16 != 0x5555)
		{
			// Start game
			lives_counter = 3
			global.sk_bluespheres = 0
		#if STANDALONE
			player.next_life_score = 5000
			unused.player2.next_life_score = 5000
		#endif

			if (D0.u16 == 0x4000 || D0.u16 == 0x4001)
			{
				// Start Blue Spheres
				if (D0.u16 == 0x4000)
				{
					// Blue Spheres from Sonic 3
					if (global.lock_on_state != 0)
					{
						// Inaccessible without lock-on
						global.sk_bluespheres = 1
					}
				}
				else if (D0.u16 == 0x4001)
				{
					// Blue Spheres from Sonic & Knuckles
					global.sk_bluespheres = 1
				}

				// Start Blue Spheres
				global.game_mode = 0x34		// Special Stage

			#if STANDALONE
				if (control.pad1.state & CONTROL_A)
				{
					// This is needed to make the special stage selection (incl. 8th stage) work
					//  -> You need to start a special stage form level select by pressing A + Start
					//  -> Current sound test selection tells the game which stage to start
					//  -> See "BlueSpheres.getNextOpenStageIndex()" function for more
					debug_mode.unlocked = 1
				}

			#else
				// Tried to start a Sonic 3 stage in S&K ?
				if (global.lock_on_state != 0)
				{
					if (global.zone_act != 0x0400 && global.zone_act < 0x0700)
					{
						global.zone_act = 0x0700		// Mushroom Hill Act 1
						global.zone_act.apparent = 0x0700
						checkpoint.zone_act = 0x0700
						checkpoint.zone_act.apparent = 0x0700
					}
				}
			#endif
				return false
			}
			else
			{
				// Normal levels
				if (fn007d6c())
				{
					return false
				}
			}
		}
	}
#if STANDALONE
	else if (control.pad1.pressed & CONTROL_B || Input.buttonPressed(BUTTON_BACK))
	{
	#if !GAMEAPP
		global.game_mode = 0x04		// Title Screen
	#endif

		playSound(0xad)

		u32 backupA0 = A0
		FadeOutScreenBlocking()
		A0 = backupA0

		// Do not fade out music in this case, Data Select music is meant to keep playing
		Game.returnToMainMenu()
		yieldExecution()
		return false
	}
#endif

	return true
}


function void LevelSelect.startGame()
{
	// Start game (for direct script calls from the application only)
	u16 zone_act = global.zone_act

	DataSelect.sharedNewGameStartup()

	// Game preparation code
	global.zone_act = zone_act
	global.zone_act.apparent = zone_act
	checkpoint.zone_act = zone_act
	checkpoint.zone_act.apparent = zone_act
	checkpoint.move_area.bottom = 0x1000
	level.backup.move_area.bottom = 0x1000
	lives_counter = 3
	unused.lives_counter.player2 = 3
	ring_counter = 0
	timer.alldata = 0
	player.score = 0
	ring_counter.player2 = 0
	timer.alldata.player2 = 0
	player2.score = 0
	continues_counter = 0
	global.rolling_demo = 0
	player.next_life_score = 5000
	unused.player2.next_life_score = 5000
	u16[0xffffff8a] = 0		// Items on (competition_mode.item_flag = 0), set game mode to Grand Prix (competition_mode.game_mode = 0)
	competition_mode.active = 0
}


//# translated(0x007d6c) end(0x007e62)
function bool fn007d6c()
{
#if !STANDALONE
	// Check if debug mode unlocked -- don't care in S3AIR
	if (u16[0xffffffe2] == 0)
	{
		bool fail = false
		if (isMainCharacter(CHARACTER_KNUCKLES))
		{
			// Knuckles can't go to Sonic's SSZ, DDZ, DEZ, or LRZ boss
			fail = (D0.u16 == 0x0a00 || D0.u16 == 0x0c00 || D0.u16 == 0x1600 || D0.u16 == 0x1700)
		}
		else
		{
			if (D0.u16 == 0x0a01)
			{
				// Sonic and Tails can't go to Knuckles' SSZ
				fail = true
			}
			else if (D0.u16 == 0x0c00)
			{
				// Tails can't go to DDZ
				fail = isMainCharacter(CHARACTER_TAILS)
			}
		}

		if (fail)
		{
			// Play error sound and prevent proceeding to zone
			playSound(0xb2)
			return false
		}
	}

	// Tried to start a Sonic 3 stage in S&K ?
	if (global.lock_on_state != 0)
	{
		// Can't go to Sonic 3 zones in Sonic & Knuckles without lock-on
		D1.u16 = D0.u16 & 0xff00
		if (D1.u16 != 0x0400 && D1.u16 < 0x0700)
		{
			// Redirect to Mushroom Hill Act 1
			D0.u16 = 0x0700
		}
	}
#endif

	D0.u16 &= 0x3fff
	global.zone_act = D0.u16
	global.zone_act.apparent = D0.u16
	checkpoint.zone_act = D0.u16
	checkpoint.zone_act.apparent = D0.u16
	checkpoint.move_area.bottom = 0x1000
	level.backup.move_area.bottom = 0x1000
	global.game_mode = 0x0c			// Main Game

	lives_counter = 3
	unused.lives_counter.player2 = 3
	ring_counter = 0
	timer.alldata = 0
	player.score = 0
	ring_counter.player2 = 0
	timer.alldata.player2 = 0
	player2.score = 0
	continues_counter = 0
	global.rolling_demo = 0
	player.next_life_score = 5000
	unused.player2.next_life_score = 5000

	playSound(MUSIC_CTRL_FADEOUT)

	u16[0xffffff8a] = 0		// Items on (competition_mode.item_flag = 0), set game mode to Grand Prix (competition_mode.game_mode = 0)
	competition_mode.active = (global.zone >= 0x0e && global.zone < 0x13)
	return true
}


//# address-hook(0x007f62) end(0x008010)
//# translated(0x008012) end(0x00803e)
function void fn007f62()
{
	A4 = 0xffff0000
	A5 = 0x008040
	A3 = A5 + levelselect.selection * 4
	D0 = u32(u8[A3]) * 0x50 + u8[A3 + 1]
	A1 = A4 + D0.s16

	D1 = u32(u8[A3]) * 0x80 + u8[A3 + 1]
	D1.u16 += 0xc000
	VDP.setupVRAMWrite(D1.u16)

	for (u8 i = 0; i < 0x0f; ++i)
	{
		D0.u16 = u16[A1] + D3.u16
		VDP.writeData16(D0.u16)
		A1 += 2
	}

	A3 += 2
	D0 = u8[A3]
	if (D0.u8 != 0)
	{
		D0 = u32(D0.u16) * 0x50 + u8[A3 + 1]
		A1 = A4 + D0.u16

		D1 = 0xc000 + u8[A3] * 0x80 + u8[A3 + 1]
		VDP.setupVRAMWrite(D1.u16)

		D0.u16 = u16[A1] + D3.u16
		VDP.writeData16(D0.u16)
		A1 += 2
	}

	if (levelselect.selection != 0x20)
	{
		// Write character select counter digits
		VDP.setupVRAMWrite(0xc19c)
		LevelSelect.writeHexDigit(levelselect.characters >> 4,   D3.u16)
		LevelSelect.writeHexDigit(levelselect.characters & 0x0f, D3.u16)
	}
	else
	{
		// Write Sound test counter digits
		VDP.setupVRAMWrite(0xcb46)
		LevelSelect.writeHexDigit(levelselect.soundtest >> 4,   D3.u16)
		LevelSelect.writeHexDigit(levelselect.soundtest & 0x0f, D3.u16)
	}
}


function void LevelSelect.writeHexDigit(u8 digit, u16 baseOffset)
{
	u16 value = digit + ((digit >= 10) ? 20 : 16)
	VDP.writeData16(baseOffset + value)
}


//# address-hook(0x007e64) end(0x007f1e)
//# translated(0x007f46) end(0x007f60)
function void fn007e64()
{
	bool skipPart = false
	if ((control.pad1.pressed & (CONTROL_UP | CONTROL_DOWN)) == 0)
	{
		--u16[0xffffff80]
		skipPart = (s16[0xffffff80] >= 0)
	}

	if (!skipPart)
	{
		u16[0xffffff80] = 11
		if (control.pad1.state & CONTROL_UP)
		{
			--levelselect.selection
			if (s16(levelselect.selection) < 0)
				levelselect.selection = 0x20
		}
		if (control.pad1.state & CONTROL_DOWN)
		{
			++levelselect.selection
			if (levelselect.selection > 0x20)
				levelselect.selection = 0
		}
		return
	}

	if (levelselect.selection == 0x20)
	{
		// Sound test
		if (control.pad1.pressed & CONTROL_LEFT)
		{
			if (levelselect.soundtest > 0)
				--levelselect.soundtest
		}
		if (control.pad1.pressed & CONTROL_RIGHT)
		{
			levelselect.soundtest = (levelselect.soundtest + 1) & 0xff
		}
		if (control.pad1.pressed & CONTROL_A)
		{
			levelselect.soundtest = (levelselect.soundtest + 0x10) & 0xff
		}

		if (control.pad1.pressed & CONTROL_C)
		{
			playMusic(levelselect.soundtest)
		}
		if (control.pad1.pressed & CONTROL_B)
		{
			playMusic(0xe3)
		}
	}
	else
	{
		if (control.pad1.pressed & (CONTROL_LEFT | CONTROL_RIGHT))
		{
			levelselect.selection = u8[0x007f22 + levelselect.selection]
		}

		if (control.pad1.pressed & CONTROL_C)
		{
			u8 maxCharacterSelection = 3
		#if STANDALONE
			// Knuckles & Tails
			if (Game.isSecretUnlocked(SECRET_KNUX_AND_TAILS))
			{
				maxCharacterSelection = 4
			}
		#endif

			levelselect.characters = (levelselect.characters < maxCharacterSelection) ? (levelselect.characters + 1) : CHARS_SONIC_AND_TAILS
		}
	}
}
